// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/message_center/notification_list.h"

#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "ui/gfx/image/image.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_blocker.h"
#include "ui/message_center/notification_types.h"

namespace message_center {

namespace {

    bool ShouldShowNotificationAsPopup(
        const Notification& notification,
        const NotificationBlockers& blockers)
    {
        for (size_t i = 0; i < blockers.size(); ++i) {
            if (!blockers[i]->ShouldShowNotificationAsPopup(notification.notifier_id()))
                return false;
        }
        return true;
    }

} // namespace

bool ComparePriorityTimestampSerial::operator()(Notification* n1,
    Notification* n2)
{
    if (n1->priority() > n2->priority()) // Higher pri go first.
        return true;
    if (n1->priority() < n2->priority())
        return false;
    return CompareTimestampSerial()(n1, n2);
}

bool CompareTimestampSerial::operator()(Notification* n1, Notification* n2)
{
    if (n1->timestamp() > n2->timestamp()) // Newer come first.
        return true;
    if (n1->timestamp() < n2->timestamp())
        return false;
    if (n1->serial_number() > n2->serial_number()) // Newer come first.
        return true;
    if (n1->serial_number() < n2->serial_number())
        return false;
    return false;
}

NotificationList::NotificationList()
    : message_center_visible_(false)
    , quiet_mode_(false)
{
}

NotificationList::~NotificationList()
{
    STLDeleteContainerPointers(notifications_.begin(), notifications_.end());
}

void NotificationList::SetMessageCenterVisible(
    bool visible,
    std::set<std::string>* updated_ids)
{
    if (message_center_visible_ == visible)
        return;

    message_center_visible_ = visible;

    if (!visible)
        return;

    for (Notifications::iterator iter = notifications_.begin();
         iter != notifications_.end(); ++iter) {
        Notification* notification = *iter;
        bool was_popup = notification->shown_as_popup();
        bool was_read = notification->IsRead();
        if (notification->priority() < SYSTEM_PRIORITY)
            notification->set_shown_as_popup(true);
        notification->set_is_read(true);
        if (updated_ids && !(was_popup && was_read))
            updated_ids->insert(notification->id());
    }
}

void NotificationList::AddNotification(scoped_ptr<Notification> notification)
{
    PushNotification(std::move(notification));
}

void NotificationList::UpdateNotificationMessage(
    const std::string& old_id,
    scoped_ptr<Notification> new_notification)
{
    Notifications::iterator iter = GetNotification(old_id);
    if (iter == notifications_.end())
        return;

    new_notification->CopyState(*iter);

    // Handles priority promotion. If the notification is already dismissed but
    // the updated notification has higher priority, it should re-appear as a
    // toast. Notifications coming from websites through the Web Notification API
    // will always re-appear on update.
    if ((*iter)->priority() < new_notification->priority() || new_notification->notifier_id().type == NotifierId::WEB_PAGE) {
        new_notification->set_is_read(false);
        new_notification->set_shown_as_popup(false);
    }

    // Do not use EraseNotification and PushNotification, since we don't want to
    // change unread counts nor to update is_read/shown_as_popup states.
    Notification* old = *iter;
    notifications_.erase(iter);
    delete old;

    // We really don't want duplicate IDs.
    DCHECK(GetNotification(new_notification->id()) == notifications_.end());
    notifications_.insert(new_notification.release());
}

void NotificationList::RemoveNotification(const std::string& id)
{
    EraseNotification(GetNotification(id));
}

NotificationList::Notifications NotificationList::GetNotificationsByNotifierId(
    const NotifierId& notifier_id)
{
    Notifications notifications;
    for (Notifications::iterator iter = notifications_.begin();
         iter != notifications_.end(); ++iter) {
        if ((*iter)->notifier_id() == notifier_id)
            notifications.insert(*iter);
    }
    return notifications;
}

bool NotificationList::SetNotificationIcon(const std::string& notification_id,
    const gfx::Image& image)
{
    Notifications::iterator iter = GetNotification(notification_id);
    if (iter == notifications_.end())
        return false;
    (*iter)->set_icon(image);
    return true;
}

bool NotificationList::SetNotificationImage(const std::string& notification_id,
    const gfx::Image& image)
{
    Notifications::iterator iter = GetNotification(notification_id);
    if (iter == notifications_.end())
        return false;
    (*iter)->set_image(image);
    return true;
}

bool NotificationList::SetNotificationButtonIcon(
    const std::string& notification_id, int button_index,
    const gfx::Image& image)
{
    Notifications::iterator iter = GetNotification(notification_id);
    if (iter == notifications_.end())
        return false;
    (*iter)->SetButtonIcon(button_index, image);
    return true;
}

bool NotificationList::HasNotificationOfType(const std::string& id,
    const NotificationType type)
{
    Notifications::iterator iter = GetNotification(id);
    if (iter == notifications_.end())
        return false;

    return (*iter)->type() == type;
}

bool NotificationList::HasPopupNotifications(
    const NotificationBlockers& blockers)
{
    for (Notifications::iterator iter = notifications_.begin();
         iter != notifications_.end(); ++iter) {
        if ((*iter)->priority() < DEFAULT_PRIORITY)
            break;
        if (!ShouldShowNotificationAsPopup(**iter, blockers))
            continue;
        if (!(*iter)->shown_as_popup())
            return true;
    }
    return false;
}

NotificationList::PopupNotifications NotificationList::GetPopupNotifications(
    const NotificationBlockers& blockers,
    std::list<std::string>* blocked_ids)
{
    PopupNotifications result;
    size_t default_priority_popup_count = 0;

    // Collect notifications that should be shown as popups. Start from oldest.
    for (Notifications::const_reverse_iterator iter = notifications_.rbegin();
         iter != notifications_.rend(); iter++) {
        if ((*iter)->shown_as_popup())
            continue;

        // No popups for LOW/MIN priority.
        if ((*iter)->priority() < DEFAULT_PRIORITY)
            continue;

        if (!ShouldShowNotificationAsPopup(**iter, blockers)) {
            if (blocked_ids)
                blocked_ids->push_back((*iter)->id());
            continue;
        }

        // Checking limits. No limits for HIGH/MAX priority. DEFAULT priority
        // will return at most kMaxVisiblePopupNotifications entries. If the
        // popup entries are more, older entries are used. see crbug.com/165768
        if ((*iter)->priority() == DEFAULT_PRIORITY && default_priority_popup_count++ >= kMaxVisiblePopupNotifications) {
            continue;
        }

        result.insert(*iter);
    }
    return result;
}

void NotificationList::MarkSinglePopupAsShown(
    const std::string& id, bool mark_notification_as_read)
{
    Notifications::iterator iter = GetNotification(id);
    DCHECK(iter != notifications_.end());

    if ((*iter)->shown_as_popup())
        return;

    // System notification is marked as shown only when marked as read.
    if ((*iter)->priority() != SYSTEM_PRIORITY || mark_notification_as_read)
        (*iter)->set_shown_as_popup(true);

    // The popup notification is already marked as read when it's displayed.
    // Set the is_read() back to false if necessary.
    if (!mark_notification_as_read)
        (*iter)->set_is_read(false);
}

void NotificationList::MarkSinglePopupAsDisplayed(const std::string& id)
{
    Notifications::iterator iter = GetNotification(id);
    if (iter == notifications_.end())
        return;

    if ((*iter)->shown_as_popup())
        return;

    if (!(*iter)->IsRead())
        (*iter)->set_is_read(true);
}

NotificationDelegate* NotificationList::GetNotificationDelegate(
    const std::string& id)
{
    Notifications::iterator iter = GetNotification(id);
    if (iter == notifications_.end())
        return NULL;
    return (*iter)->delegate();
}

void NotificationList::SetQuietMode(bool quiet_mode)
{
    quiet_mode_ = quiet_mode;
    if (quiet_mode_) {
        for (Notifications::iterator iter = notifications_.begin();
             iter != notifications_.end();
             ++iter) {
            (*iter)->set_shown_as_popup(true);
        }
    }
}

Notification* NotificationList::GetNotificationById(const std::string& id)
{
    Notifications::iterator iter = GetNotification(id);
    if (iter != notifications_.end())
        return *iter;
    return NULL;
}

NotificationList::Notifications NotificationList::GetVisibleNotifications(
    const NotificationBlockers& blockers) const
{
    Notifications result;
    for (Notifications::const_iterator iter = notifications_.begin();
         iter != notifications_.end(); ++iter) {
        bool should_show = true;
        for (size_t i = 0; i < blockers.size(); ++i) {
            if (!blockers[i]->ShouldShowNotification((*iter)->notifier_id())) {
                should_show = false;
                break;
            }
        }
        if (should_show)
            result.insert(*iter);
    }

    return result;
}

size_t NotificationList::NotificationCount(
    const NotificationBlockers& blockers) const
{
    return GetVisibleNotifications(blockers).size();
}

size_t NotificationList::UnreadCount(
    const NotificationBlockers& blockers) const
{
    Notifications notifications = GetVisibleNotifications(blockers);
    size_t unread_count = 0;
    for (Notifications::const_iterator iter = notifications.begin();
         iter != notifications.end(); ++iter) {
        if (!(*iter)->IsRead())
            ++unread_count;
    }
    return unread_count;
}

NotificationList::Notifications::iterator NotificationList::GetNotification(
    const std::string& id)
{
    for (Notifications::iterator iter = notifications_.begin();
         iter != notifications_.end(); ++iter) {
        if ((*iter)->id() == id)
            return iter;
    }
    return notifications_.end();
}

void NotificationList::EraseNotification(Notifications::iterator iter)
{
    delete *iter;
    notifications_.erase(iter);
}

void NotificationList::PushNotification(scoped_ptr<Notification> notification)
{
    // Ensure that notification.id is unique by erasing any existing
    // notification with the same id (shouldn't normally happen).
    Notifications::iterator iter = GetNotification(notification->id());
    bool state_inherited = false;
    if (iter != notifications_.end()) {
        notification->CopyState(*iter);
        state_inherited = true;
        EraseNotification(iter);
    }
    // Add the notification to the the list and mark it unread and unshown.
    if (!state_inherited) {
        // TODO(mukai): needs to distinguish if a notification is dismissed by
        // the quiet mode or user operation.
        notification->set_is_read(false);
        notification->set_shown_as_popup(message_center_visible_
            || quiet_mode_
            || notification->shown_as_popup());
    }
    // Take ownership. The notification can only be removed from the list
    // in EraseNotification(), which will delete it.
    notifications_.insert(notification.release());
}

} // namespace message_center
